package org.beanspirit.web;

import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.beanspirit.constant.Constant;
import org.beanspirit.data.LoanDetailVO;
import org.beanspirit.data.LoanVO;
import org.beanspirit.data.SummaryVO;
import org.beanspirit.enumeration.LoanType;
import com.google.gson.Gson;

public class LoanAction extends HttpServlet {
  private static final long serialVersionUID = -2634808849300422731L;

  private LoanVO calculateAnnuityLoan(BigDecimal totalLoan, Integer tenure, BigDecimal interestRate) {
    LoanVO vo = new LoanVO();
    BigDecimal ratePercentage = interestRate.divide(Constant.HUNDRED, 5, RoundingMode.HALF_DOWN);
    BigDecimal divisor = BigDecimal.ONE.subtract(BigDecimal.ONE.divide(
        BigDecimal.ONE.add(ratePercentage.divide(Constant.TWELVE, 10, RoundingMode.HALF_DOWN)).pow(
            tenure), 20, RoundingMode.HALF_DOWN));
    BigDecimal installment = totalLoan.multiply(
        ratePercentage.divide(Constant.TWELVE, 10, RoundingMode.HALF_DOWN)).divide(divisor, 10,
        RoundingMode.HALF_DOWN);
    BigDecimal totalInterest = BigDecimal.ZERO;
    BigDecimal totalInstallment = BigDecimal.ZERO;
    BigDecimal remaining = totalLoan;
    for (int i = 0; i < tenure; i++) {
      BigDecimal interest = remaining.multiply(ratePercentage).divide(Constant.TWELVE, 10,
          RoundingMode.HALF_UP);
      BigDecimal principal = installment.subtract(interest);
      remaining = remaining.subtract(principal);
      LoanDetailVO detail = new LoanDetailVO(principal.setScale(2, RoundingMode.DOWN),
          interest.setScale(2, RoundingMode.UP), installment.setScale(2, RoundingMode.DOWN),
          remaining.setScale(2, RoundingMode.HALF_DOWN));
      totalInterest = totalInterest.add(interest);
      vo.addDetail(detail);
    }
    totalInstallment = totalLoan.add(totalInterest);
    SummaryVO summary = new SummaryVO(totalLoan.setScale(2, RoundingMode.UP),
        totalInterest.setScale(2, RoundingMode.UP), totalInstallment.setScale(2, RoundingMode.UP));
    vo.setSummary(summary);
    return vo;
  }

  private LoanVO calculateEffectiveLoan(BigDecimal totalLoan, Integer tenure,
      BigDecimal interestRate) {
    BigDecimal principal = totalLoan.divide(new BigDecimal(tenure), 5, RoundingMode.HALF_DOWN);
    BigDecimal monthlyInterest = interestRate.divide(Constant.TWELVE, 5, RoundingMode.HALF_DOWN);
    LoanVO vo = new LoanVO();
    BigDecimal totalInterest = BigDecimal.ZERO;
    BigDecimal totalInstallment = BigDecimal.ZERO;
    BigDecimal remaining = totalLoan;
    for (int i = 0; i < tenure; i++) {
      BigDecimal interest = monthlyInterest.multiply(remaining).divide(Constant.HUNDRED, 5,
          RoundingMode.HALF_DOWN);
      BigDecimal installment = interest.add(principal);
      remaining = remaining.subtract(principal);
      LoanDetailVO detail = new LoanDetailVO(principal.setScale(2, RoundingMode.DOWN),
          interest.setScale(2, RoundingMode.UP), installment.setScale(2, RoundingMode.DOWN),
          remaining.setScale(2, RoundingMode.HALF_DOWN));
      totalInterest = totalInterest.add(interest);
      vo.addDetail(detail);
    }
    totalInstallment = totalLoan.add(totalInterest);
    SummaryVO summary = new SummaryVO(totalLoan.setScale(2, RoundingMode.UP),
        totalInterest.setScale(2, RoundingMode.UP), totalInstallment.setScale(2, RoundingMode.UP));
    vo.setSummary(summary);
    return vo;
  }

  private LoanVO calculateFixedLoan(BigDecimal totalLoan, Integer tenure, BigDecimal interestRate) {
    BigDecimal tenureVal = new BigDecimal(tenure);
    BigDecimal principal = totalLoan.divide(tenureVal, 5, RoundingMode.HALF_DOWN);
    BigDecimal interest = interestRate.multiply(totalLoan).divide(Constant.HUNDRED)
        .divide(Constant.TWELVE, 5, RoundingMode.HALF_DOWN);
    BigDecimal installment = principal.add(interest);
    LoanVO vo = new LoanVO();
    BigDecimal totalInterest = BigDecimal.ZERO;
    BigDecimal totalInstallment = BigDecimal.ZERO;
    BigDecimal remaining = totalLoan;
    for (int i = 0; i < tenure; i++) {
      remaining = remaining.subtract(principal);
      LoanDetailVO detail = new LoanDetailVO(principal.setScale(2, RoundingMode.DOWN),
          interest.setScale(2, RoundingMode.UP), installment.setScale(2, RoundingMode.DOWN),
          remaining.setScale(2, RoundingMode.HALF_DOWN));
      vo.addDetail(detail);
    }
    totalInterest = interestRate.multiply(totalLoan)
        .multiply(tenureVal.divide(Constant.TWELVE, 5, RoundingMode.HALF_DOWN))
        .divide(Constant.HUNDRED, RoundingMode.HALF_UP);
    totalInstallment = totalLoan.add(totalInterest);
    SummaryVO summary = new SummaryVO(totalLoan.setScale(2, RoundingMode.UP),
        totalInterest.setScale(2, RoundingMode.UP), totalInstallment.setScale(2, RoundingMode.UP));
    vo.setSummary(summary);
    return vo;
  }

  private LoanVO calculateLoan(BigDecimal totalLoan, Integer tenure, BigDecimal interestRate,
      LoanType loanType) {
    if (loanType == LoanType.FIXED) {
      return calculateFixedLoan(totalLoan, tenure, interestRate);
    } else if (loanType == LoanType.EFFECTIVE) {
      return calculateEffectiveLoan(totalLoan, tenure, interestRate);
    } else if (loanType == LoanType.ANNUITY) {
      return calculateAnnuityLoan(totalLoan, tenure, interestRate);
    }
    return null;
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
    LoanType[] loanTypes = LoanType.values();
    req.setAttribute("loanTypes", loanTypes);
    req.getRequestDispatcher("/WEB-INF/jsp/loan.jsp").forward(req, resp);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
    String totalLoanStr = req.getParameter("totalLoan");
    String tenureStr = req.getParameter("tenure");
    String interestRateStr = req.getParameter("interest");
    String loanTypeStr = req.getParameter("loanType");

    BigDecimal totalLoan = new BigDecimal(totalLoanStr);
    Integer tenure = new Integer(tenureStr);
    BigDecimal interestRate = new BigDecimal(interestRateStr);
    LoanType loanType = LoanType.valueOf(loanTypeStr);
    LoanVO loan = calculateLoan(totalLoan, tenure, interestRate, loanType);
    Gson gson = new Gson();
    String json = gson.toJson(loan);
    PrintWriter out = resp.getWriter();
    out.write(json);
    out.flush();
  }
}
